package pers.mihao.quickstream.common.plus.lock.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import pers.mihao.quickstream.common.plus.lock.annotation.LockKey;
import pers.mihao.quickstream.common.plus.lock.exception.UnGetLockException;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import pers.mihao.quickstream.common.plus.lock.GlobalLock;
import pers.mihao.quickstream.common.plus.lock.annotation.EnableGlobalLock;
import pers.mihao.quickstream.common.plus.log.annotation.PersistentLog;
import pers.mihao.quickstream.common.plus.log.model.AbstractLog;
import pers.mihao.quickstream.common.util.CollectionUtil;
import pers.mihao.quickstream.common.util.DateUtil;
import pers.mihao.quickstream.common.util.ReflectionUtil;
import pers.mihao.quickstream.common.util.StringUtil;
import pers.mihao.quickstream.common.util.biz.CurrUserIdHolder;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 写日志的拦截器
 *
 * @author mihao
 */
@Component
@Aspect
@Slf4j
public class EnableLogAspect {

    @Resource
    GlobalLock globalLock;

    @Pointcut("@annotation(pers.mihao.quickstream.common.plus.lock.annotation.EnableGlobalLock))")
    public void EnableGlobalLock() {
    }

    @Around("EnableGlobalLock()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Method proxyMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Method targetMethod = joinPoint.getTarget().getClass().getMethod(proxyMethod.getName(),
                proxyMethod.getParameterTypes());

        EnableGlobalLock enableGlobalLock = targetMethod.getAnnotation(EnableGlobalLock.class);
        if (enableGlobalLock != null) {
            String key = getLockKey(targetMethod, joinPoint.getArgs());
            log.info("获取全局锁 key {}", key);
            boolean tryLock = globalLock.tryLock(key);
            if (tryLock) {
                try {
                    log.info("获取全局锁成功 {}", key);
                    return joinPoint.proceed();
                } finally {
                    globalLock.unlock(key);
                    log.info("释放全局锁成功 {}", key);
                }
            } else {
                throw new UnGetLockException("获取全局锁错误" + key);
            }

        } else {
            return joinPoint.proceed();
        }
    }

    private String getLockKey(Method targetMethod, Object[] args) {
        Parameter[] parameters = targetMethod.getParameters();
        String argsType = StringUtil.joinWith(",", Arrays.stream(parameters).map(Parameter::getType).map(Class::getName)
                .collect(Collectors.toList()));
        List<String> lockKeys = new ArrayList<>(List.of(targetMethod.getDeclaringClass().getName() + "#" + targetMethod
                + argsType));
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            LockKey lockKey = parameter.getAnnotation(LockKey.class);
            if (lockKey != null) {
                lockKeys.add(lockKey.value() + "$" + Optional.ofNullable(args[i]).map(Object::toString).orElse(""));
            }
        }

        return StringUtil.joinWith(",", lockKeys);
    }

    private void doRecordLog(ProceedingJoinPoint joinPoint, PersistentLog persistentLog,
                             long end, long start, Method method, Object res) throws IllegalAccessException, InstantiationException {
        AbstractLog log = ReflectionUtil.getNewInstance(persistentLog.bean());
        if (StringUtil.isNotBlack(persistentLog.tableName())) {
            log.setTableName(persistentLog.tableName());
        }
        if (StringUtil.isNotBlack(persistentLog.dataSource())) {
            log.setDataSource(persistentLog.dataSource());
        }
        log.setAsync(persistentLog.isAsync());
        log.setUnderscore(persistentLog.isUnderscore());
        log.setCreateTime(DateUtil.getDataTime());
        log.setExecTime(end - start);
        log.setInvokeMethod(method.getName());
        log.setServiceName(joinPoint.getThis().toString());
        log.setTriggerUserId(CurrUserIdHolder.getUserId());
        log.setArgs(joinPoint.getArgs());
        log.setRes(res);
        ReflectionUtil.getSingleton(log.getInvoke()).doPersistLog(log);
    }
}